//
//  TTURLImageLoader.m
//  Tint
//
//  Created by Kevin Wu on 3/12/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTURLImageLoader.h"


#define TTURLImageLoaderDownloadAddress @"address"
#define TTURLImageLoaderDownloadRequest @"request"
#define TTURLImageLoaderDownloadClaimItems @"claim_items"
#define TTURLImageLoaderDownloadCacheTimeoutInterval @"cache_timeout_interval"

#define TTURLImageLoaderClaimTarget @"target"
#define TTURLImageLoaderClaimSuccessSelector @"success_selector"
#define TTURLImageLoaderClaimFailureSelector @"failure_selector"
#define TTURLImageLoaderClaimFailureImage @"failure_image"


@interface TTURLImageLoader (Private)
- (NSDictionary *)downloadItemByAddress:(NSString *)address;
- (NSDictionary *)downloadItemByRequest:(TTHTTPRequest *)request;
- (NSDictionary *)downloadItemByTarget:(id)target;

- (NSDictionary *)claimItemByTarget:(id)target;
- (NSDictionary *)claimItemInClaimItems:(NSArray *)claimItems byTarget:(id)target;

- (void)downloadSuccess:(NSDictionary *)downloadItem;
- (void)downloadFailed:(NSDictionary *)downloadItem;

- (void)setImageForDownloadItem:(NSDictionary *)downloadItem;
- (void)setImage:(UIImage *)image forTarget:(id)target selector:(SEL)selector;

- (void)setLoading:(BOOL)loading forTarget:(id)target;
- (void)setLoading:(BOOL)loading forClaimItems:(NSArray *)claimItems;

@end

@implementation TTURLImageLoader (Private)

- (NSDictionary *)downloadItemByAddress:(NSString *)address
{
    return [_downloadItemMap objectForKey:address];
}

- (NSDictionary *)downloadItemByRequest:(TTHTTPRequest *)request
{
    for ( NSDictionary *downloadItem in [_downloadItemMap objectEnumerator] ) {
        TTHTTPRequest *tmpRequest = [downloadItem objectForKey:TTURLImageLoaderDownloadRequest];
        if ( request == tmpRequest ) {
            return downloadItem;
        }
    }
    return nil;
}

- (NSDictionary *)downloadItemByTarget:(id)target
{
    for ( NSDictionary *downloadItem in [_downloadItemMap objectEnumerator] ) {
        NSArray *claimItems = [downloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
        for ( NSDictionary *claimItem in claimItems ) {
            id tmpTarget = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
            if ( tmpTarget == target ) {
                return downloadItem;
            }
        }
    }
    return nil;
}


- (NSDictionary *)claimItemByTarget:(id)target
{
    for ( NSDictionary *downloadItem in [_downloadItemMap objectEnumerator] ) {
        NSArray *claimItems = [downloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
        for ( NSDictionary *claimItem in claimItems ) {
            id tmpTarget = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
            if ( tmpTarget == target ) {
                return claimItem;
            }
        }
    }
    return nil;
}

- (NSDictionary *)claimItemInClaimItems:(NSArray *)claimItems byTarget:(id)target
{
    for ( NSDictionary *claimItem in claimItems ) {
        id tmpTarget = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
        if ( tmpTarget == target ) {
            return claimItem;
        }
    }
    return nil;
}



- (void)downloadSuccess:(NSDictionary *)downloadItem
{
    if ( downloadItem ) {
        
        NSTimeInterval cacheTimeoutInterval = [[downloadItem objectForKey:TTURLImageLoaderDownloadCacheTimeoutInterval] doubleValue];
        
        NSString *address = [downloadItem objectForKey:TTURLImageLoaderDownloadAddress];
        NSString *cacheKey = [[self class] cacheKeyFromAddress:address];
        
        if ( ![[TTDiskCache sharedObject] hasCacheForKey:cacheKey name:TTURLImageCacheName] ) {
            NSString *cachePath = [TTDiskCache cachePathForKey:cacheKey name:TTURLImageCacheName];
            [[TTDiskCache sharedObject] moveFileAtPath:cachePath 
                                                 asKey:cacheKey 
                                   withTimeoutInterval:cacheTimeoutInterval 
                                                  name:TTURLImageCacheName];
        }
        
        [self setImageForDownloadItem:downloadItem];
        
        
        [_downloadItemMap removeObjectForKey:address];
    }
    
}

- (void)downloadFailed:(NSDictionary *)downloadItem
{
    if ( downloadItem ) {
        
        NSArray *claimItems = [downloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
        
        for ( NSDictionary *claimItem in claimItems ) {
            
            id target = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
            SEL failureSelector = NSSelectorFromString([claimItem objectForKey:TTURLImageLoaderClaimFailureSelector]);
            UIImage *failureImage = [claimItem objectForKey:TTURLImageLoaderClaimFailureImage];
            
            [self setLoading:NO forTarget:target];
            [self setImage:failureImage forTarget:target selector:failureSelector];
            
        }
        
        NSString *address = [downloadItem objectForKey:TTURLImageLoaderDownloadAddress];
        [_downloadItemMap removeObjectForKey:address];
    }
    
}


- (void)setImageForDownloadItem:(NSDictionary *)downloadItem
{
    
    if ( downloadItem ) {
        NSString *address = [downloadItem objectForKey:TTURLImageLoaderDownloadAddress];
        NSString *cacheKey = [[self class] cacheKeyFromAddress:address];
        
        NSArray *claimItems = [downloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
        
        NSString *imagePath = [TTDiskCache cachePathForKey:cacheKey name:TTURLImageCacheName];
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:imagePath];
        if ( image ) {
            
            for ( NSDictionary *claimItem in claimItems ) {
                
                id target = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
                SEL successSelector = NSSelectorFromString([claimItem objectForKey:TTURLImageLoaderClaimSuccessSelector]);
                
                [self setLoading:NO forTarget:target];
                [self setImage:image forTarget:target selector:successSelector];
                
            }
            
        } else {
            
            for ( NSDictionary *claimItem in claimItems ) {
                
                id target = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
                SEL failureSelector = NSSelectorFromString([claimItem objectForKey:TTURLImageLoaderClaimFailureSelector]);
                UIImage *failureImage = [claimItem objectForKey:TTURLImageLoaderClaimFailureImage];
                
                [self setLoading:NO forTarget:target];
                [self setImage:failureImage forTarget:target selector:failureSelector];
                
            }
            
        }
        
        TTRelease(image);
    }
    
    
}

- (void)setImage:(UIImage *)image forTarget:(id)target selector:(SEL)selector
{
    if ( (target) && (selector) && [target respondsToSelector:selector] ) {
        [target performSelector:selector withObject:image];
    }
}



- (void)setLoading:(BOOL)loading forTarget:(id)target
{
    if ( (target) && [target respondsToSelector:@selector(setImageLoading:)] ) {
        [(id<TTURLImageLoaderTargetProtocol>)target setImageLoading:loading];
    }
}

- (void)setLoading:(BOOL)loading forClaimItems:(NSArray *)claimItems
{
    for ( NSDictionary *claimItem in claimItems ) {
        id target = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
        [self setLoading:loading forTarget:target];
    }
}

@end





@implementation TTURLImageLoader



TTSynthesizeSingleton(TTURLImageLoader);


#pragma mark - Accessors

@synthesize defaultCacheTimeoutInterval = _defaultCacheTimeoutInterval;

- (void)setDefaultCacheTimeoutInterval:(NSTimeInterval)defaultCacheTimeoutInterval
{
    _defaultCacheTimeoutInterval = defaultCacheTimeoutInterval;
    if ( _defaultCacheTimeoutInterval <= 0.0 ) {
        _defaultCacheTimeoutInterval = TTTimeIntervalWeek();
    }
}


#pragma mark - Memory

- (id)init
{
    self = [super init];
    if ( self ) {
        _defaultCacheTimeoutInterval = TTTimeIntervalWeek();
        _downloadItemMap = [[NSMutableDictionary alloc] init];
        
        // Create cache directory if needed
        [TTDiskCache createCacheRootDirectory:TTURLImageCacheName];
    }
    return self;
}

- (void)dealloc
{
    _defaultCacheTimeoutInterval = 0.0;
    TTRelease(_downloadItemMap);
    [super dealloc];
}



#pragma mark - Public

- (void)loadImageWithAddress:(NSString *)address
{
    [self loadImageWithAddress:address 
          cacheTimeoutInterval:_defaultCacheTimeoutInterval 
                        target:nil 
                       success:NULL 
                       failure:NULL 
                  failureImage:nil];
}

- (void)loadImageWithAddress:(NSString *)address 
        cacheTimeoutInterval:(NSTimeInterval)cacheTimeoutInterval 
                      target:(id)target 
                     success:(SEL)success 
                     failure:(SEL)failure 
                failureImage:(UIImage *)failureImage 
{
    
    // Check address
    if ( !TTIsStringWithText(address) ) {
        // Change image loading flag
        [self setLoading:NO forTarget:target];
        [self setImage:failureImage forTarget:target selector:failure];
        return;
    }
    
    TTBeginAutoreleasePool();
    
    // Find old download
    NSDictionary *oldDownloadItem = [self downloadItemByTarget:target];
    
    
    // Set up new download
    NSDictionary *downloadItem = [self downloadItemByAddress:address];
    if ( downloadItem == nil ) {
        downloadItem = [[NSMutableDictionary alloc] init];
        
        [(NSMutableDictionary *)downloadItem setObject:address forKey:TTURLImageLoaderDownloadAddress];
        
        NSMutableArray *claimItems = [[NSMutableArray alloc] init];
        [(NSMutableDictionary *)downloadItem setObject:claimItems forKey:TTURLImageLoaderDownloadClaimItems];
        TTRelease(claimItems);
        
        
        [_downloadItemMap setObject:downloadItem forKey:address];
        TTRelease(downloadItem);
        downloadItem = [self downloadItemByAddress:address];
    }
    
    NSTimeInterval interval = (cacheTimeoutInterval <= 0.0) ? _defaultCacheTimeoutInterval : cacheTimeoutInterval;
    NSNumber *timeoutInterval = [[NSNumber alloc] initWithDouble:interval];
    [(NSMutableDictionary *)downloadItem setObject:timeoutInterval forKey:TTURLImageLoaderDownloadCacheTimeoutInterval];
    TTRelease(timeoutInterval);
    
    
    NSMutableArray *claimItems = [downloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
    NSDictionary *claimItem = [self claimItemInClaimItems:claimItems byTarget:target];
    if ( claimItem == nil ) {
        claimItem = [[NSMutableDictionary alloc] init];
        
        [(NSMutableDictionary *)claimItem setObject:target forKey:TTURLImageLoaderClaimTarget];
        
        [claimItems addObject:claimItem];
        TTRelease(claimItem);
        claimItem = [self claimItemInClaimItems:claimItems byTarget:target];
    }
    
    if ( success ) {
        [(NSMutableDictionary *)claimItem setObject:NSStringFromSelector(success) forKey:TTURLImageLoaderClaimSuccessSelector];
    } else {
        [(NSMutableDictionary *)claimItem removeObjectForKey:TTURLImageLoaderClaimSuccessSelector];
    }
    
    if ( failure ) {
        [(NSMutableDictionary *)claimItem setObject:NSStringFromSelector(failure) forKey:TTURLImageLoaderClaimFailureSelector];
    } else {
        [(NSMutableDictionary *)claimItem removeObjectForKey:TTURLImageLoaderClaimFailureSelector];
    }
    
    if ( failureImage ) {
        [(NSMutableDictionary *)claimItem setObject:failureImage forKey:TTURLImageLoaderClaimFailureImage];
    } else {
        [(NSMutableDictionary *)claimItem removeObjectForKey:TTURLImageLoaderClaimFailureImage];
    }
    
    
    // Cancel if needed
    if ((oldDownloadItem) && 
        (oldDownloadItem != downloadItem))
    {
        NSMutableArray *claimItems = [oldDownloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
        NSDictionary *claimItem = [self claimItemInClaimItems:claimItems byTarget:target];
        [self setLoading:NO forTarget:target];
        [claimItems removeObject:claimItem];
        
        if ( [claimItems count] < 1 ) {
            TTHTTPRequest *request = [oldDownloadItem objectForKey:TTURLImageLoaderDownloadRequest];
            [request removeDelegate:self];
            [request cancel];
            
            NSString *oldAddress = [oldDownloadItem objectForKey:TTURLImageLoaderDownloadAddress];
            [_downloadItemMap removeObjectForKey:oldAddress];
        }
    }
    
    
    
    // Check cache
    NSString *cacheKey = [[self class] cacheKeyFromAddress:address];
    
    if ( [[TTDiskCache sharedObject] hasCacheForKey:cacheKey name:TTURLImageCacheName] ) {
        // Image is in cache
        [self setImageForDownloadItem:downloadItem];
        
        NSString *address = [downloadItem objectForKey:TTURLImageLoaderDownloadAddress];
        [_downloadItemMap removeObjectForKey:address];
        
    } else {
        // Image is not in cache
        
        [self setLoading:YES forTarget:target];
        
        TTHTTPRequest *request = [downloadItem objectForKey:TTURLImageLoaderDownloadRequest];
        if (request) {
            // downloading
        } else {
            // Set up request to download
            
            TTHTTPRequest *request = [[TTHTTPRequest alloc] initWithAddress:address];
            request.destinationPath = [TTDiskCache cachePathForKey:cacheKey name:TTURLImageCacheName];
            [request addDelegate:self];
            [request startAsynchronous];
            [(NSMutableDictionary *)downloadItem setObject:request forKey:TTURLImageLoaderDownloadRequest];
            TTRelease(request);
        }
        
    }
    
    TTEndAutoreleasePool();
}



- (void)cancelDownloadingAddress:(NSString *)address
{
    NSDictionary *downloadItem = [self downloadItemByAddress:address];
    if ( downloadItem ) {
        
        NSArray *claimItems = [downloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
        for ( NSDictionary *claimItem in claimItems ) {
            
            id target = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
            SEL failureSelector = NSSelectorFromString([claimItem objectForKey:TTURLImageLoaderClaimFailureSelector]);
            UIImage *failureImage = [claimItem objectForKey:TTURLImageLoaderClaimFailureImage];
            
            [self setLoading:NO forTarget:target];
            [self setImage:failureImage forTarget:target selector:failureSelector];
            
        }
        
        TTHTTPRequest *request = [downloadItem objectForKey:TTURLImageLoaderDownloadRequest];
        [request removeDelegate:self];
        [request cancel];
        
        [_downloadItemMap removeObjectForKey:address];
    }
}

- (void)cancelRequestingTarget:(id)target
{
    if ( (target) && [target respondsToSelector:@selector(imageLoading)] ) {
        BOOL loading = [(id<TTURLImageLoaderTargetProtocol>)target imageLoading];
        if ( !loading ) {
            return;
        }
    }
    
    NSDictionary *downloadItem = [self downloadItemByTarget:target];
    if ( downloadItem ) {
        NSMutableArray *claimItems = [downloadItem objectForKey:TTURLImageLoaderDownloadClaimItems];
        NSDictionary *claimItem = [self claimItemInClaimItems:claimItems byTarget:target];
        
        if ( claimItem ) {
            
            id target = [claimItem objectForKey:TTURLImageLoaderClaimTarget];
            SEL failureSelector = NSSelectorFromString([claimItem objectForKey:TTURLImageLoaderClaimFailureSelector]);
            UIImage *failureImage = [claimItem objectForKey:TTURLImageLoaderClaimFailureImage];
            
            [self setLoading:NO forTarget:target];
            [self setImage:failureImage forTarget:target selector:failureSelector];
            
            [claimItems removeObject:claimItem];
            
            if ( [claimItems count] < 1 ) {
                
                TTHTTPRequest *request = [downloadItem objectForKey:TTURLImageLoaderDownloadRequest];
                [request removeDelegate:self];
                [request cancel];
                
                NSString *address = [downloadItem objectForKey:TTURLImageLoaderDownloadAddress];
                [_downloadItemMap removeObjectForKey:address];
                
            }
        }
    }
}


- (BOOL)isAddressDownloading:(NSString *)address
{
    return ( [self downloadItemByAddress:address] != nil );
}

- (BOOL)isTargetRequesting:(id)target
{
    return ( [self downloadItemByTarget:target] != nil );
}



+ (NSString *)cacheKeyFromAddress:(NSString *)address
{
    NSString *md5 = [address newMD5HashString];
    NSString *uppercase = [md5 uppercaseString];
    TTRelease(md5);
    return uppercase;
}

- (void)removeCachedFileOfAddress:(NSString *)address
{
    NSString *cacheKey = [[self class] cacheKeyFromAddress:address];
    [[TTDiskCache sharedObject] removeCacheForKey:cacheKey name:TTURLImageCacheName];
}




#pragma mark - TTOperationDelegate

//- (void)operationDidStart:(TTOperation *)operation
//{
//}

//- (void)operationUpdated:(TTOperation *)operation
//{
//}

- (void)operationDidFail:(TTOperation *)operation withError:(NSError *)error
{
    NSDictionary *downloadItem = [self downloadItemByRequest:(TTHTTPRequest *)operation];
    [self downloadFailed:downloadItem];
}

//- (void)operationWillFinish:(TTOperation *)operation
//{
//}

- (void)operationDidFinish:(TTOperation *)operation
{
    NSDictionary *downloadItem = [self downloadItemByRequest:(TTHTTPRequest *)operation];
    [self downloadSuccess:downloadItem];
}

@end
